package com.weic.easycache.access;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.StatusLine;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

import com.weic.easycache.Debug;
import com.weic.easycache.exception.ApiErrorException;
import com.weic.easycache.exception.ApiStatusException;
import com.weic.easycache.exception.HttpStatusException;
import com.weic.easycache.framework.Config;
import com.weic.easycache.framework.web.ApiStatus;
import com.weic.easycache.file.Gzip;
import com.weic.easycache.http.Http;

public abstract class AbsNetAces implements INetAces {
	protected static final String LOG_TAG = "AbsNetAces";
	protected static final boolean LOG = Debug.LOG;

	private boolean jsonAndStatus = true;
	private boolean gzipEnabled = true;

	public AbsNetAces(boolean jsonAndStatus, boolean gzipEnabled) {
		this.jsonAndStatus = jsonAndStatus;
		this.gzipEnabled = gzipEnabled;
	}

	protected static class HttpConnResp {
		public final HttpResponse response;
		public final HttpURLConnection connection;
		public HttpConnResp(HttpResponse response, HttpURLConnection connection) {
			this.response = response;
			this.connection = connection;
		}
	}

	@Override
	public final String executeAccess(String url, String serviceName, int method, Map<String, String> headers,
			String[] paramNames, String[] paramValues) throws HttpStatusException,
			ApiStatusException, ApiErrorException, ConnectTimeoutException,
			SocketTimeoutException, UnknownHostException, IOException {
		if(LOG) Log.i(LOG_TAG, "[executeAccess]"+url+serviceName);
		HttpConnResp conresp = execAccess(url, serviceName, method, headers, paramNames, paramValues, false);

		HttpEntity entity = conresp.response.getEntity();
		//注意httpEntity.getContent() 有可能是 connection.getErrorStream();
		byte[] bytes = EntityUtils.toByteArray(entity);
		String charset = EntityUtils.getContentCharSet(entity);
		if(charset == null) charset = getDefaultEncoding();
		
		String result = null;
		boolean error = false;
		try {
			if(getContentIsGzip(entity)) {	//gzipEnabled
				try {
					result = Gzip.uncompress(bytes, charset);
				}catch (IOException e) {
					//throw new ApiErrorException(new String(bytes, charset));
					result = new String(bytes, charset);
					error = true;
				}
			}else {
				result = new String(bytes, charset);	//EntityUtils.toString(httpEntity);
			}
		}finally {
			if(conresp.connection != null) conresp.connection.disconnect();
		}
		if(jsonAndStatus) {	//&& getContentIsJson(entity)不要这个条件。如果前端必须要json，而这里不是json必须报异常
			try {
				checkJsonAndStatus(result);
			} catch (JSONException e) {	//说明是从connection.getErrorStream()返回的数据
				throw new ApiErrorException(result);
			}
		}else if(error) {
			throw new ApiErrorException(result);
		}
		return result;
	}

	@Override
	public Input getInputStream(String url, String serviceName,
			Map<String, String> headers, String[] paramNames, String[] paramValues)
					throws HttpStatusException, ConnectTimeoutException,
					SocketTimeoutException, UnknownHostException, IOException {
		//下面getFileName()将用到。若serviceName为空，且url请求的是图片等资源，后面不应该加"/"
		serviceName = serviceName==null ? "" : serviceName;
		HttpResponse response = execAccess(url, serviceName, Method.GET, headers, paramNames, paramValues, true).response;

		Header[] responseHeaders = response.getAllHeaders();

		if(LOG) for(Header header : responseHeaders) {
			Log.i(LOG_TAG, "HEADER:"+header.getName()+", "+header.getValue());
		}

		long position = -1, toPosition = -1, duration = -1;
		boolean acceptRange = false;
		for(Header header : responseHeaders) {
			if(header.getName().equalsIgnoreCase(Http.ResponseHeader.K_Accept_Ranges) &&
					header.getValue().equalsIgnoreCase(Http.ResponseHeader.V_Accept_Ranges_bytes)) {
				acceptRange = true;
			}else if(header.getName().equalsIgnoreCase(Http.ResponseHeader.K_Content_Length)) {
				duration = Integer.valueOf(header.getValue());
			}else if(header.getName().equalsIgnoreCase(Http.ResponseHeader.K_Content_Range)) {
				String contentRange = header.getValue();	//bytes 100-500/800
				String[] strings = contentRange.split("[ -/]");
				if(strings!=null && strings.length==4 &&
						strings[0].equalsIgnoreCase(Http.ResponseHeader.V_Accept_Ranges_bytes)) {
					acceptRange = true;
					position = Integer.valueOf(strings[1]);
					toPosition = Integer.valueOf(strings[2]);
					duration = duration<0 ? Integer.valueOf(strings[3]) : duration;
				}
			}
		}
		Input input = new Input(response.getEntity().getContent(), acceptRange, position,
				toPosition, duration);
		input.fileName = getFileName(url+serviceName, responseHeaders);
		return input;
	}

	protected abstract HttpConnResp execAccess(String url, int method, Map<String, String> headers,
			List<NameValuePair> entityParams) throws UnknownHostException,
			ConnectTimeoutException, SocketTimeoutException, IOException;

	private final HttpConnResp execAccess(String url, String serviceName, int method, Map<String, String> headers,
			String[] paramNames, String[] paramValues, boolean loadfile) throws HttpStatusException,
			ConnectTimeoutException, SocketTimeoutException, UnknownHostException, IOException {
		url = reviseUrl(url, serviceName, method, paramNames, paramValues);
		List<NameValuePair> entityParams = makeEntityParams(method, paramNames, paramValues);
		HttpConnResp conResp = execAccess(url, method, headers, entityParams);
		checkResponseStatus(conResp.response.getStatusLine(), loadfile);
		return conResp;
	}

	protected String reviseUrl(String url, String serviceName, int method, String[] paramNames, String[] paramValues) {
		serviceName = serviceName==null ? "" : serviceName;
		url += serviceName;	//若serviceName为空，且url请求的是图片等资源，后面不应该加"/"
		switch(method) {
		case Method.DEPRECATED_GET_OR_POST:
		case Method.POST:
		case Method.PUT:
			return url;
		case Method.GET:
		case Method.DELETE:
			int len = paramNames==null ? 0 : paramNames.length;
			if(len > 0) {
				StringBuffer _url = new StringBuffer(url + "?");
				for(int i=0; i<len; i++) {
					if(i > 0) {
						_url.append("&" + paramNames[i] + "=" + paramValues[i]);
					}else {
						_url.append(paramNames[i] + "=" + paramValues[i]);
					}
				}
				url = _url.toString();
			}
			return url;
		default:
			throw new IllegalArgumentException("参数 method 不正确，详见 INetAces.Method");
		}
	}

	protected List<NameValuePair> makeEntityParams(int method, String[] paramNames, String[] paramValues) {
		switch(method) {
		case Method.DEPRECATED_GET_OR_POST:
		case Method.POST:
		case Method.PUT:
			int len = paramNames==null ? 0 : paramNames.length;
			if(len > 0) {
				List<NameValuePair> entityParams = new ArrayList<NameValuePair>();
				for(int i=0; i<len; i++) {
					entityParams.add(new BasicNameValuePair(paramNames[i], paramValues[i]));
				}
				return entityParams;
			}
			return null;
		case Method.GET:
		case Method.DELETE:
			return null;
		default:
			throw new IllegalArgumentException("参数 method 不正确，详见 INetAces.Method");
		}
	}

	protected void checkResponseStatus(StatusLine statusLine, boolean loadfile) throws HttpStatusException {
		int statusCode = statusLine.getStatusCode();
		//if(statusCode==200 || statusCode==302 || (loadfile&&(statusCode==206)))
		if(statusCode==HttpStatus.SC_OK || statusCode==HttpStatus.SC_MOVED_TEMPORARILY
				|| (loadfile&&(statusCode==HttpStatus.SC_PARTIAL_CONTENT))) {
			return;
		}else {
			throw new HttpStatusException(statusCode, statusLine.getReasonPhrase());
		}
	}

	protected void checkJsonAndStatus(String resultJson) throws ApiStatusException, JSONException {
		if(LOG) Log.i(LOG_TAG, "[checkStatus]"+resultJson);
		int status = new JSONObject(resultJson).getInt(Config.get().STATUS);
		if(status == Config.get().SUCCESS.code || ApiStatus.isIrgnored(status)) {
			return;
		}else {
			for(ApiStatus resultStatus : Config.get().getApiStatus()) {
				if(resultStatus.code == status) {
					throw new ApiStatusException(resultStatus);
				}
			}
		}
	}

	protected boolean checkIsJson(String s) {
		if(LOG) Log.i(LOG_TAG, "[checkIsJson]"+s);
		try {
			new JSONObject(s);
			return true;
		} catch (JSONException e) {
			e.printStackTrace();
		}
		try {
			new JSONArray(s);
			return true;
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return false;
	}

	/** 获取文件名 */
	private String getFileName(String url, Header[] headers) {
		String filename = url.substring(url.lastIndexOf('/')+1);
		if(filename.indexOf('?')>0) filename = filename.substring(0, filename.indexOf('?'));

		if(filename == null || filename.trim().length() == 0) {	//如果获取不到文件名称
			Pattern pattern = Pattern.compile(".*filename=(.*)");
			for (Header header : headers) {
				if(header.getName().equalsIgnoreCase(Http.ResponseHeader.K_Content_Disposition)) {
					Matcher m = pattern.matcher(header.getValue());
					if(m.find()) {	//查找括号“(.*)”里面匹配的字符串
						filename = m.group(1);
						return filename;
					}
				}
			}
			filename = null;	//UUID.randomUUID() + ".tmp";//默认取一个文件名
		}
		return filename;
	}

	/* "application/json; abc/xx; charset=utf-8; aa=utffdf-8"
	 * 第一个分号前面的会通过getName()拿到，后面的都会放到getParameters()返回的NameValuePair[]里*/
	protected static boolean getContentIsJson(final HttpEntity entity) throws ParseException {
		boolean json = false;
		if (entity.getContentType() != null) {
			HeaderElement values[] = entity.getContentType().getElements();
			if (values.length > 0) {
				json = values[0].getName().endsWith("/json");
			}
		}
		return json;
	}

	protected static boolean getContentIsGzip(final HttpEntity entity) throws ParseException {
		boolean gzip = false;
		if (entity.getContentEncoding() != null) {
			HeaderElement values[] = entity.getContentEncoding().getElements();
			for (HeaderElement e : values) {
				gzip = e.getName().equals("gzip");
				if(gzip) break;
			}
		}
		return gzip;
	}

	protected static String getDefaultEncoding() {
		return Config.get().netParamsEncoding;
	}

	protected static String getBodyContentType() {
		return URLEncodedUtils.CONTENT_TYPE + HTTP.CHARSET_PARAM + getDefaultEncoding();
	}

	protected static Map<String, String> getDefaultHeaders() {
		return Config.get().getDefaultHeaders();
	}

	protected static int getTimeoutMs() {
		return Config.get().netTimeoutMs;
	}

	protected static String getUserAgent() {
		return Config.get().packageName + "/" + Config.get().versionName;
	}
}
